#include "mvc/controller/controller.h"
#include "mvc/model/model.h"
#include "mvc/view/view.h"
#include "mvc/model/project.h"
#include "common/types.h"
#include "common/settings.h"
#include "common/log/QsLog.h"
#include "mvc/model/aircrafts/aircraft_rcurves.h"

#ifdef _WIN32
#include "windows.h"
#endif

#include <sys/stat.h>
#include <iostream>
#include <fstream>
#include <QFileInfo>
#include <QTimer>
#include <QSignalMapper>
#include <QDir>
#include <QProcess>
#include <QMessageBox>
#include <QDebug>
#include <QApplication>
#include <QProgressDialog>
#include <QThread>
#include <common/xmlparser.h>

using namespace std;

Controller::Controller(QObject *parent): QObject(parent)
{
    _principal_controller = new Principal_controller();
    _parameters_controller = new Parameters_controller();
    _configuration_controller = new Configuration_controller();
    _cpu = new configurations_parameters_uavs;
    //QTread::sl
}


Controller::~Controller()
{
    //delete this;
}

void Controller::set_model(Model *model)
{
    _model = model;
}

void Controller::set_view(View *view)
{
    _view = view;
}

/*
 *
 *methods
 *
 */

void Controller::connect_signals()
{
    connect((QObject *) _view, SIGNAL(s_new_project(QString, QString)), this, SLOT(create_new_project(QString, QString)));
    connect((QObject *) _view, SIGNAL(s_open_project(QString, int)), this, SLOT(open_project(QString, int)));
    connect((QObject *) _view, SIGNAL(s_save_project()), this, SLOT(save_project()));
    connect((QObject *) _view, SIGNAL(s_load_parameters(ParametersWidget *, Airfoils_curve_map *,  int )),
            this, SLOT(load_parameters_widget(ParametersWidget *, Airfoils_curve_map *,  int )));
    connect((QObject *) _view, SIGNAL(s_load_configuration(int)), this, SLOT(load_configuration_to_view(int)));

    connect((QObject *) _view, SIGNAL(s_draw_configuration(int)), this, SLOT(draw_configuration(int)));
    connect((QObject *) _view, SIGNAL(s_update_configuration(int, void *)), SLOT(update_configuration(int, void*)));

    connect((QObject *) _view, SIGNAL(s_simulate(int, configurations_parameters_uavs *)),
            this, SLOT(simulate(int, configurations_parameters_uavs *)));


    // connect((QObject *) _view, SIGNAL(s_next_step(int)), this, SLOT(next_step(int)));// ESTA DE MAS ME PARECE
    connect((QObject *) _view, SIGNAL(s_play_pause(int)), this, SLOT(start_simulation(int)));
    connect((QObject *) _view, SIGNAL(s_rewind(int)), this, SLOT(rewind_simulation(int)));
    connect(_view, SIGNAL(s_add_simulation_zones_to_model(int, QVector<time_zone*>)), this,
            SLOT(add_simulation_zones_to_model(int, QVector<time_zone*>)));

    connect(this, SIGNAL(s_update_wait_dialog(int, QString)), this, SLOT(update_wait_dialog(int, QString)));
}

void Controller::listen_view()
{
    //std::cout << "LISTEN VIEW IN CONTROLLER" << std::endl;
    _principal_controller->listen_principal_windows();
}

/**
  * Action to create a new project
  */
void Controller::create_new_project(const QString &project_name, const QString &module)
{
    QString pdir = projects_directory();
    QString fdir;
    QString file;

    _model->clean_model();

    if(!project_name.contains("/"))
    {
#ifdef Q_OS_LINUX
        mkdir(QString(pdir).append(project_name).toStdString().c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
#elif _WIN32
        QDir dir;
        dir.mkdir(QString(pdir).append(project_name));
#endif
        fdir = project_name;
        file = project_name;

        QLOG_INFO() << "Created directory to new project: " << QString(pdir).append(project_name);
    }
    else
    {
        fdir = QString(project_name).remove(project_name.lastIndexOf("/"), project_name.size());
        file = QString(project_name).remove(0, project_name.lastIndexOf("/") + 1);

        QLOG_INFO() << "Not need to create directory, already exists";
    }

    file.append(".").append(module);

    pdir.append(fdir).append("/");
    ofstream myfile;
    myfile.open(QString(pdir).append(file).toStdString().c_str());
    myfile.close();

    QLOG_INFO() << "Created file: " << QString(pdir).append(file);

    _model->create_project(fdir, file);
}

/**
  * Action to open a existing project
  */
void Controller::open_project(const QString &project_name, int module)
{
    QLOG_INFO() << "Open project : " << project_name;
    _model->clean_model();
    QString pdir = projects_directory();
    pdir.append(project_name);

    QString module_name = get_module_key(module);
    QLOG_INFO() << "Open " << module_name << " project: " << project_name << " from " << pdir;

    QFile file(pdir);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QLOG_ERROR() << "Cannot find file controller.open_project: " << pdir;
        QMessageBox::information(0, tr("Error"),
                                 tr("Cannot open the project ").append(project_name),
                                 QMessageBox::Ok);
        file.close();
    }

    QTextStream in(&file);
    QString configs_size = in.readLine();
    QString airfoils_size = in.readLine();

    int n_configs = (QString(configs_size).remove(0, configs_size.lastIndexOf("=") + 1)).toInt();
    int n_airfoils = (QString(airfoils_size).remove(0, airfoils_size.lastIndexOf("=") + 1)).toInt();

    QMap<QString, QString> _airfoils; // Airfoil - Airfoil.dat
    for(int i = 0; i < n_airfoils; i++)
    {
        QString c_line = in.readLine();

        QString c_airfoil = QString(c_line).remove(c_line.lastIndexOf("="), c_line.size());
        QString c_airfoiln = QString(c_line).remove(0, c_line.lastIndexOf("=") + 2);

        _airfoils.insert(c_airfoil.trimmed(), c_airfoiln.trimmed());
    }

    QString analysis_variables_size = in.readLine();
    int n_analysis_variables = (QString(analysis_variables_size).
                                remove(0, analysis_variables_size.lastIndexOf("=") + 1)).toInt();

    QVector<QPair<QString, QString> *> analysis_variables;
    QPair<QString, QString> *_pair;
    for(int i = 0; i < n_analysis_variables; i++)
    {
        QString c_line = in.readLine();

        QString section_parameter = QString(c_line).remove(0, c_line.lastIndexOf("=") + 1);
        section_parameter.trimmed();

        QStringList list_section_variable = section_parameter.split(",");

        _pair = new QPair<QString, QString>(list_section_variable.at(0).trimmed(),
                                            list_section_variable.at(1).trimmed());

        analysis_variables.push_back(_pair);
    }

    _model->set_analysis_parameters(analysis_variables);

    file.close();

    _model->open_project(project_name, n_configs);
    const Project current_project = _model->current_project();

    const QVector<Parameters *> values = current_project.read_params_file("model.xml");

    Airfoils_curve_map *airfoils_data = _view->get_airfoils_list_point(_airfoils);

    _model->make_configurations(values, airfoils_data, module);

    for(int c_config = 0; c_config < n_configs; c_config++)
    {
      QString file(DATS_PREFIX);
      file.append("CONFIG.DAT");

      bool exists_config = _model->current_project().file_exist(c_config + 1, file);

      if(exists_config)
          _model->c_simulated(c_config + 1);
    }

    if(_model->is_allc_simulated())
        _view->enable_graphic_all_configuration();

    _view->show_configs(values);
}

/**
  * Actions to save the current project.
  */
void Controller::save_project()
{
    //cout << "TODO: Controller.save_project" << endl;
    _model->generate_configurations();
    QMessageBox::information(0, _model->current_project().file_name(),
                             tr("All configurations were generated"),
                             QMessageBox::Ok);
}

/**
  *
  */
void Controller::update_configuration(int configuration, void *values)
{
    cout << "TODO: Controller.update configuration" << endl;
}

/**
  * Simulate a configuration or all of them.
  */
void Controller::simulate(int configuration, configurations_parameters_uavs *cpu)
{
    current_configuration = configuration;

    if (cpu->sim_all_configs)
    {
        int last_configuration = _model->total_configurations();
        current_configuration = 1;

        while (current_configuration <= last_configuration)
        {
            _model->draw_configuration(current_configuration);
            simulate_configuration(current_configuration, cpu);
            _uav_process->waitForFinished(-1);
            _model->c_simulated(current_configuration);
            current_configuration++;
        }
    }
    else
    {
        simulate_configuration(current_configuration, cpu);
        _model->c_simulated(current_configuration);
    }

    if(_model->is_allc_simulated())
        _view->enable_graphic_all_configuration();
}

/**
  * Actions to start the ejecution of the simulator.
  */
void Controller::simulate_configuration(int configuration, configurations_parameters_uavs *config_parameters)
{
    //    cout << "TODO: Controller.simulate" << endl;
    _cpu = config_parameters;
    _configuration_controller->make_file_config(config_parameters, _model->current_project().get_config_dir(configuration));
    _sim_step = 1;
    run_proces(configuration, _cpu->process_name);
    _view->get_principal_window()->enable_simulator_actions();
}

/**
  * Actions to start the view of the simulation.
  */
void Controller::start_simulation(int configuration)
{
    //    cout << "Controller.start simulation configuration: " << configuration << endl;
    if (!_view->get_principal_window()->get_paused()) {
        _timer->stop();
    }
    else{
        initialize_timer();
        _configuration_controller->read_file_config(_cpu, _model->current_project().get_config_dir(configuration));// LEVANTAR CONFIG.DAT
        int duration_step = calculate_viewing_time() * 1000; // 1000 es para pasar de milisegundos a segundos
        int total_steps = _cpu->t_box; // Obtener total steps
        float scale = _cpu->longitud_caracteristica; // Obtener scale
        _model->load_simulation(configuration, total_steps, scale);

        QSignalMapper* signalMapper = new QSignalMapper (this);
        _timer->setInterval(duration_step);
        connect (_timer, SIGNAL(timeout()), signalMapper, SLOT(map()));
        signalMapper -> setMapping (_timer, configuration);
        connect (signalMapper, SIGNAL(mapped(int)), this, SLOT(next_step(int)));
        _timer->start();

    }

}

/*
 * Actions to do in the next step of the simulation.
 */
void Controller::next_step(int configuration){
    if (_sim_step > _cpu->t_box){
        _timer->stop();
        //rewind_simulation();
        _view->get_principal_window()->enable_simulator_actions();
    }
    else{
        _view->get_principal_window()->set_sim_step(_sim_step);
        _model->draw_configuration(configuration, _sim_step, _cpu->longitud_caracteristica);
        _sim_step++;
    }
}

/**
  * Set simulator variables to their initial values
  */
void Controller::rewind_simulation(int configuration){
    //    cout << "Rewind simulation " << endl;
    _model->draw_configuration(configuration);
    _view->get_principal_window()->set_sim_step(0);
    _sim_step = 1;
}

/**
  * Initialize timer and sim_step label
  */
void Controller::initialize_timer(){
    _timer = new QTimer(this);
    _sim_step = 1;
}

/**
  * Calculate viewen time between steps
  */
double Controller::calculate_viewing_time(){
    double real_time;
    real_time = _cpu->longitud_caracteristica / _cpu->velocidad_caracteristica;
    _view->get_principal_window()->set_real_time(real_time);
    return real_time * _view->get_principal_window()->get_factor();;
}

/**
  * Run the generator of stelas.
  */
void Controller::run_proces(int configuration, QString process_name){
    //    std::cout << "CORRIENDO PROCESO" << std::endl;
    QStringList arguments;
    arguments << DATS_PREFIX;

    _uav_process = new QProcess(this);

    QString path(_model->current_project().get_config_dir(configuration));
    QString programa;

    //    std::cout << "PATH UAVS: " << path.toStdString() << std::endl;
    _uav_process->setWorkingDirectory(path);

#ifdef Q_OS_LINUX
    programa = process_name;
#elif WIN32
    programa = process_name.append(".exe");  //PARA WINDOWS
#endif

    connect(_uav_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(process_finished(int,  QProcess::ExitStatus)));
    connect(_uav_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(process_error(QProcess::ProcessError)));
    connect(_uav_process, SIGNAL(readyReadStandardOutput()),this,SLOT(readyReadStandardOutput()));
    connect(_uav_process, SIGNAL(readyReadStandardError()),this,SLOT(readyReadStandardError()));

    _uav_process->start(programa, arguments);
    // uav_process->execute(programa, arguments);
    start_wait_dialog();
    canceled_process = false;
}

/**
  * Read the standar output of the uav process
  */
void Controller::readyReadStandardOutput(){
    std::cout << "-----STANDARD OUTPUT----- " << std::endl;
    QByteArray aux = _uav_process->readAllStandardOutput();
    QString output(aux);
    output = output.simplified();
    qDebug()<< aux;
    emit(s_update_wait_dialog(_sim_step, output));
    if (aux.contains("Printing output-data")){
        _sim_step++;
        QApplication::restoreOverrideCursor();
    }
}

/**
  * Read the standar error output of the uav process
  */
void Controller::readyReadStandardError(){
    std::cout << "-----ERROR OUTPUT----- " << std::endl;
    qDebug() << _uav_process->readAllStandardError();
}

/**
  * Initialize the Wait Dialog
  */
void Controller::start_wait_dialog(){

    QApplication::setOverrideCursor(Qt::WaitCursor);
    _wait_dialog = new QProgressDialog(_view->get_principal_window());
    _wait_dialog->setFixedSize(400,200);
    _wait_dialog->setLabelText(tr("Running uav process..."));
    //_wait_dialog->setAutoClose(false);
    _wait_dialog->setRange(0, _cpu->t_box + 1);
    _wait_dialog->setModal(true);
    _wait_dialog->setValue(0);
    connect(_wait_dialog, SIGNAL(canceled()), this, SLOT(wait_dialog_canceled()));
}

/*
 * Updates the wait dialog value
 */
void Controller::update_wait_dialog(int sim_step, QString output){
    QString config_name("RID");   //PONER LA CONFIGURACION COMO GLOBAL PORQUE ES UN QUILOMBO LLEVARLA A TODOS LADOS
    if(current_configuration < 10)
        config_name.append("00");
    else if(current_configuration> 9 && current_configuration< 100)
        config_name.append("0");
    config_name.append(QString::number(current_configuration));
    QString text(tr("Running uav process: "));
    text.append(config_name +"\n\n" + output);
    _wait_dialog->setValue(sim_step);
    _wait_dialog->setLabelText(text);
}

/*
 * Actions to do when close button in wait_dialog is pressed
 */
void Controller::wait_dialog_canceled() {
    canceled_process = true;
    _uav_process->terminate();
    _wait_dialog->close();
}

/**
  *
  */
void Controller::add_simulation_zones_to_model(int config, QVector<time_zone*> zones)
{
    _model->add_simulation_zones(config, zones);
}

/**
  *
  */
void Controller::load_parameters_widget(ParametersWidget *parameters, Airfoils_curve_map *airfoil_point_list,
                                        int module)
{
    QVector<QVector<float> > configurations(0);
    QVector<QPair<QString, QString> *> intervals_parameters;
    configurations =  _parameters_controller->get_configurations(parameters, &intervals_parameters);
    QVector<Parameters*> parameters_uav = _parameters_controller->make_parameters_uavs(parameters, configurations);
    QLOG_INFO() << "Calculated Total configurations: " << configurations.size();

    _model->set_analysis_parameters(intervals_parameters);

    for(int i = 1; i < configurations.size() + 1; i++)
    {
        QString dir = _model->current_project().get_config_dir(i);

#ifdef Q_OS_LINUX
        mkdir(dir.toStdString().c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
#elif WIN32
        QDir dirPath;
        dirPath.mkdir(dir);
#endif

        XmlParser::getInstance().writeXml("model", dir, *parameters_uav.at(i-1));
    }

    ofstream _file;

    QString path = _model->current_project().full_path();

    _file.open(path.toStdString().c_str());

    _file << "total_configurations  = " << configurations.size() << endl;
    _file << "total_airfoils = " << airfoil_point_list->size() << endl;

    QMap<QString, QString> airfoils_names = (*parameters)["airfoils"];
    QList<QString> _keys = airfoils_names.keys();
    for(int i = 0; i < _keys.size(); i++)
        _file << _keys.at(i).toStdString().c_str() << " = " <<
                 airfoils_names[_keys.at(i)].toStdString().c_str() << endl;

    _file << "total_analysis_variables = " << intervals_parameters.size() << endl;
    for(int i = 0; i < intervals_parameters.size(); i++)
        _file << "analysis_variable = " << intervals_parameters.at(i)->first.toStdString().c_str() << ","
                 << intervals_parameters.at(i)->second.toStdString().c_str() << endl;

    _file.close();

    _model->make_configurations(parameters_uav, airfoil_point_list, module );
    _view->show_configs(parameters_uav);
}

/*
 * Load values of CONFIG.DAT and pass this values to view component.
 */
void Controller::load_configuration_to_view(int configuration){
    _cpu = new configurations_parameters_uavs;
    bool file_exist =_configuration_controller->read_file_config(_cpu, _model->current_project().get_config_dir(configuration));// LEVANTAR CONFIG.DAT
    _view->simulate_configuration(_cpu, file_exist);
}

/**
  *
  */
void Controller::draw_configuration(int configuration)
{
    try
    {
        _model->draw_configuration(configuration);
    }
    catch(Non_Existent_Geometry &e) {}
    bool file_created = false;
    QString aux_dir = DATS_PREFIX;
    file_created = _model->current_project().file_exist(configuration, aux_dir.append("WAKE.TEC"));// CONTROLAR SI EXISTE WAKE PARA PERMITIR SIMULAR
    if (file_created){
        _view->get_principal_window()->enable_simulator_actions();
        _configuration_controller->read_file_config(_cpu,_model->current_project().get_config_dir(configuration));
        double real_time;
        real_time = _cpu->longitud_caracteristica / _cpu->velocidad_caracteristica;
        _view->get_principal_window()->set_real_time(real_time);
    }
    else
        _view->get_principal_window()->disable_simulator_actions();
}

/**
  * Actions when uav process is finished.
  */
void Controller::process_finished(int exit_code, QProcess::ExitStatus exitStatus){
    std::cout << "Process finished. Exit code:" << exit_code << std::endl;
    std::cout << "Process finished. Exit status: " << exitStatus << std::endl;
    QApplication::restoreOverrideCursor();
    _wait_dialog->close();
}

/**
  * Actions to do when uav process is finished with an error.
  */
void Controller::process_error(QProcess::ProcessError error_code){
    std::cout << "Error code: "<< error_code << std::endl;
    QApplication::restoreOverrideCursor();
    QMessageBox msgBox;
    msgBox.setIconPixmap(QPixmap(":/images/Rocket.png"));
    if (error_code == 0){
        _wait_dialog->setValue(1);
        _wait_dialog->close();
        msgBox.setText(tr("The process selected does not exist or does not have execute permissions.\n"
                          "Please, try again."));
        msgBox.exec();
    }

    if (!canceled_process){
        msgBox.setText(tr("The UAV procces fails.\n"
                          "Please, try again."));
        msgBox.exec();
    }
    _view->get_principal_window()->disable_simulator_actions();
}
